/*
 *  Copyright 2001-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package ar.uba.fi.posgrado.common.service.impl;

import java.util.Date;
import java.util.List;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.log4j.Logger;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import ar.uba.fi.posgrado.academy.model.Body;
import ar.uba.fi.posgrado.academy.model.Course;
import ar.uba.fi.posgrado.common.dao.DAOException;
import ar.uba.fi.posgrado.common.dao.NoSuchEntityException;
import ar.uba.fi.posgrado.common.dao.StudentDAO;
import ar.uba.fi.posgrado.common.exception.MissingArgumentException;
import ar.uba.fi.posgrado.common.exception.PosgradoException;
import ar.uba.fi.posgrado.common.exception.PosgradoRuntimeException;
import ar.uba.fi.posgrado.common.exception.RepeatedInstanceException;
import ar.uba.fi.posgrado.common.exception.RepeatedNameException;
import ar.uba.fi.posgrado.common.model.Student;
import ar.uba.fi.posgrado.common.model.User;
import ar.uba.fi.posgrado.common.service.StudentService;
import ar.uba.fi.posgrado.common.util.CriterionBuilder;
import ar.uba.fi.posgrado.common.util.ExpressionOperator;
import ar.uba.fi.posgrado.economy.exception.PosgradoRuleException;
import ar.uba.fi.posgrado.economy.model.Customer;
import ar.uba.fi.posgrado.economy.service.CustomerService;

public class StudentServiceImpl implements StudentService {

	private static Logger logger = Logger.getLogger(StudentServiceImpl.class);

	private StudentDAO studentDAO;

	private CustomerService customerService;

	/**
	 * @return the customerService
	 */
	public CustomerService getCustomerService() {
		return customerService;
	}

	/**
	 * @param customerService
	 *            the customerService to set
	 */
	public void setCustomerService(CustomerService customerService) {
		this.customerService = customerService;
	}

	/**
	 * 
	 * @return el dao de alumnos
	 */
	public StudentDAO getStudentDAO() {
		return studentDAO;
	}

	/**
	 * 
	 * @param studentDAO
	 */
	public void setStudentDAO(StudentDAO studentDAO) {
		this.studentDAO = studentDAO;
	}

	public StudentServiceImpl() {

	}

	/**
	 * Constructor para prueba unitaria
	 * 
	 * @param studentDAO
	 */
	public StudentServiceImpl(StudentDAO studentDAO,
			CustomerService customerService) {
		this.setStudentDAO(studentDAO);
		this.setCustomerService(customerService);
	}

	@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
	public void create(Student student) throws MissingArgumentException,
			PosgradoException {
		if ((student.getPersonalId().equals(""))
				|| (student.getSurname().trim().equals(""))
				|| (student.getName().trim().equals(""))) {
			throw new MissingArgumentException(
					"El documento, el nombre y el apellido son campos obligatorios");
		}

		try {
			Student otherStudent = this.getStudentDAO().findByPersonalId(
					student.getPersonalId());
			if (otherStudent.getNationality().equals(student.getNationality())
					&& otherStudent.getIdType().equals(student.getIdType()))
				throw new RepeatedInstanceException(
						"Ya existe un alumno con el documento "
								+ student.getPersonalId()
								+ ", y coinciden el tipo y nacionalidad");
		} catch (NoSuchEntityException e) {
			// si no encuentra el alumno, se puede crear (no existe ninguno con
			// ese documento)
		}
		try {
			student.setRegisterDate(new Date()); // HOY!
			this.getStudentDAO().save(student);
			logger.info("Alumno: " + student.getFullName()
					+ " creado con exito");
		} catch (DAOException e) {
			throw new PosgradoException("Excepcion al crear el alumno: "
					+ student, e);
		}
	}

	@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
	public void delete(Student student) throws PosgradoRuntimeException,
			PosgradoException {
		try {
			Student studentToDelete = this.getStudentDAO().findByPersonalId(
					student.getPersonalId());

			if (studentToDelete.getCourses().size() > 0) {
				String message = "Este alumno esta anotado en, al menos, un curso. No puede borrarse del sistema.";
				logger.info(message);
				throw new PosgradoRuleException(message);
			}

			if (studentToDelete.getCoursings().size() > 0) {
				String message = "Este alumno esta registrado en, al menos, un acta. No puede borrarse del sistema.";
				logger.info(message);
				throw new PosgradoRuleException(message);
			}

			this.getStudentDAO().remove(studentToDelete);
		} catch (ConstraintViolationException e) {
			StringBuffer mensaje = new StringBuffer("El alumno  ");
			mensaje.append(student.getFullName());
			mensaje.append(" está asociado a alguna entidad (curso, ");
			mensaje.append("materia, etc) del sistema. No puede ser borrado.");
			throw new RepeatedNameException(mensaje.toString());
		} catch (NoSuchEntityException e) {
			throw new PosgradoException(
					"Error al borrar alumno. No se encontro ningun alumno con documento '"
							+ student.getPersonalId() + "' en el sistema", e);
		}
	}

	public List<Student> getAll() throws PosgradoRuntimeException,
			PosgradoException {
		List<Student> students = null;
		try {
			students = this.getStudentDAO().findAll();
		} catch (DAOException e) {
			throw new PosgradoException(
					"Ocurrio un error al obtener todos los alumnos", e);
		}
		return students;
	}

	public Student getById(Long id) throws PosgradoRuntimeException,
			PosgradoException {
		Student alumno = null;
		try {
			alumno = (Student) this.getStudentDAO().findById(id);
		} catch (DAOException e) {
			throw new PosgradoException("Ocurrio un error al obtener "
					+ "el alumno con Id " + id, e);
		}
		return alumno;
	}

	@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
	public Student update(Student student) throws PosgradoRuntimeException,
			PosgradoException, NoSuchEntityException, RepeatedNameException {

		if (student.getPersonalId().trim().equals("")
				|| student.getSurname().trim().equals("")) {
			String mensaje = "El documento y el Apellido son datos obligatorios para dar de alta un Alumno";
			MissingArgumentException e = new MissingArgumentException(mensaje);
			throw e;
		}
		Student toUpdateStudent = this.getById(student.getId());
		if (!student.getPersonalId().equals(toUpdateStudent.getPersonalId())) {
			try {
				Student s = this.getByPersonalId(student.getPersonalId());
				if (s.getId() != toUpdateStudent.getId()) {
					throw new RepeatedNameException(
							"No se puede actualizar el alumno. Ya hay otro alumno en el sistema con el documento "
									+ student.getPersonalId());
				}
			} catch (NoSuchEntityException e) {
				// significa que esta todo bien y puede actualizar el alumno
				toUpdateStudent.setName(student.getName());
				toUpdateStudent.setSurname(student.getSurname());
				toUpdateStudent.setPersonalId(student.getPersonalId());
				toUpdateStudent.setAddress(student.getAddress());
				toUpdateStudent.setNationality(student.getNationality());
				toUpdateStudent.setPhone(student.getPhone());
				toUpdateStudent.setWorkPhone(student.getWorkPhone());
				toUpdateStudent.setMobile(student.getMobile());
				toUpdateStudent.setEmail(student.getEmail());
				toUpdateStudent.setEmail2(student.getEmail2());
				toUpdateStudent.setCompany(student.getCompany());
				toUpdateStudent.setPosition(student.getPosition());
				toUpdateStudent.setDegree(student.getDegree());
				toUpdateStudent.setIdType(student.getIdType());
				toUpdateStudent.setComplete(student.getComplete());

				this.getStudentDAO().update(toUpdateStudent);
				student = toUpdateStudent;
			}
		} else {
			toUpdateStudent.setName(student.getName());
			toUpdateStudent.setSurname(student.getSurname());
			toUpdateStudent.setPersonalId(student.getPersonalId());
			toUpdateStudent.setAddress(student.getAddress());
			toUpdateStudent.setNationality(student.getNationality());
			toUpdateStudent.setPhone(student.getPhone());
			toUpdateStudent.setWorkPhone(student.getWorkPhone());
			toUpdateStudent.setMobile(student.getMobile());
			toUpdateStudent.setEmail(student.getEmail());
			toUpdateStudent.setEmail2(student.getEmail2());
			toUpdateStudent.setCompany(student.getCompany());
			toUpdateStudent.setPosition(student.getPosition());
			toUpdateStudent.setDegree(student.getDegree());
			toUpdateStudent.setIdType(student.getIdType());
			toUpdateStudent.setComplete(student.getComplete());

			this.getStudentDAO().update(toUpdateStudent);
			student = toUpdateStudent;
		}
		return student;
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public Student getByPersonalId(String studentPersonalId)
			throws PosgradoRuntimeException, PosgradoException {
		Student response = null;
		response = this.getStudentDAO().findByPersonalId(studentPersonalId);
		if (logger.isInfoEnabled()) {
			logger.info("Un Alumno encontrado con documento: "
					+ studentPersonalId);
		}
		return response;
	}

	/**
	 * @see ar.uba.fi.posgrado.common.service.StudentService#getBySurname(java.lang.String)
	 */
	public List<Student> getBySurname(String surname)
			throws PosgradoRuntimeException, PosgradoException {
		List<Student> response = null;
		try {
			CriterionBuilder builder = new CriterionBuilder();
			builder.add("surname", surname, ExpressionOperator.ILike);
			response = this.getStudentDAO().findByCriterions(
					builder.toCriterions());

			if (logger.isDebugEnabled()) {
				logger.debug("# Alumnos con apellido " + surname + " = "
						+ response.size());
			}
		} catch (DAOException e) {
			throw new PosgradoException(
					"Excepcion buscando alumnos con criterios", e);
		}

		return response;
	}

	/**
	 * @see ar.uba.fi.posgrado.common.service.StudentService#createStudentAndCustomer(Student,
	 *      Customer)
	 */
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
	public void createStudentAndCustomer(Student student, Customer customer)
			throws PosgradoException, PosgradoRuntimeException {
		this.create(student);
		this.customerService.create(customer);
	}

	/**
	 * @see ar.uba.fi.posgrado.common.service.GenericService#filter(java.util.List,
	 *      ar.uba.fi.posgrado.common.model.User)
	 */
	public List<Student> filter(List<Student> list, User user)
			throws PosgradoRuntimeException, PosgradoException {
		if (!user.hasBodyRole()) {
			return list;
		}
		/*
		 * Si alguno de los cursos al q esta inscripto el alumno pertenece al
		 * ente del usuario entonces se muestra, caso contrario, no se muestra
		 */
		final Body body = user.getBody();
		if (logger.isDebugEnabled()) {
			logger
					.debug("El usuario tiene el rol Ente. Filtrando alumnos para el "
							+ "ente: " + body);
		}

		CollectionUtils.filter(list, new Predicate() {
			public boolean evaluate(Object object) {
				Student student = (Student) object;

				Set<Course> courses = student.getCourses();
				// Verifica si alguno de sus cursos pertenece al ente
				for (Course course : courses) {
					if (course.getBody().equals(body)) {
						return true;
					}
				}

				return false;
			}
		});

		return list;
	}

}
